#include <stdlib.h>
#include <stdio.h>
#include <Windows.h>
#include <math.h>

#include <GL\glew.h>
#include <GL\wglext.h>
#include <GL\freeglut.h>

#define PI 3.1415926535898

#pragma region base objects

class GLApplication
{
private:
	LARGE_INTEGER _last_time, _cur_time, _frequency;

public:
	GLApplication() {}
	virtual ~GLApplication() {}

public:
	void init()
	{
		onBeforeInit();			

		QueryPerformanceFrequency(&_frequency);
		QueryPerformanceCounter(&_last_time);
		QueryPerformanceCounter(&_cur_time);

		glClearColor(0.0, 0.0, 0.0, 0.0);

		onAfterInit();
	}

	void reshape(int w, int h)
	{
		onBeforeReshape(w, h);			

		glViewport(0, 0, (GLsizei)w, (GLsizei)h);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(-w / 2.0, w / 2.0, -h / 2.0, h / 2.0, -1.0, 1.0);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		onAfterReshape(w, h);
	}

	void mouse(int button, int state, int x, int y)
	{
		switch (button) {
		case GLUT_LEFT_BUTTON:
			if (state == GLUT_DOWN)
				onMouseLeftDown(x, y);
			else if (state == GLUT_UP)
				onMouseLeftUp(x, y);
			break;
		case GLUT_RIGHT_BUTTON:
			if (state == GLUT_DOWN)
				onMouseRightDown(x, y);
			else if (state == GLUT_UP)
				onMouseRightUp(x, y);
			break;
		case GLUT_MIDDLE_BUTTON:
			if (state == GLUT_DOWN)
				onMouseMiddleDown(x, y);
			else if (state == GLUT_UP)
				onMouseMiddleUp(x, y);
			break;
		case 3:
			onMouseScrollUp(x, y);
			break;
		case 4:
			onMouseScrollDown(x, y);
			break;
		default:
			break;
		}
	}

	void update()
	{
		QueryPerformanceCounter(&_cur_time);
		double elapsed = (_cur_time.QuadPart - _last_time.QuadPart) / double(_frequency.QuadPart);

		onUpdate(elapsed);			

		glutPostRedisplay();
		_last_time = _cur_time;
	}

	void render()
	{
		glClear(GL_COLOR_BUFFER_BIT);

		onBeforeRender();			
		onRender();
		
		glFinish();
		glutSwapBuffers();
		onAfterRender();
	}

protected:
	void virtual onBeforeInit() {  }
	void virtual onAfterInit() {  }

	void virtual onBeforeReshape(int w, int h) {  }
	void virtual onAfterReshape(int w, int h) {  }

	void virtual onMouseLeftUp(int x, int y) {  }
	void virtual onMouseLeftDown(int x, int y) {  }
	void virtual onMouseRightUp(int x, int y) {  }
	void virtual onMouseRightDown(int x, int y) {  }
	void virtual onMouseMiddleUp(int x, int y) {  }
	void virtual onMouseMiddleDown(int x, int y) {  }
	void virtual onMouseScrollUp(int x, int y) {  }
	void virtual onMouseScrollDown(int x, int y) {  }

	void virtual onUpdate(float dt) {  }

	void virtual onBeforeRender() {  }
	void virtual onRender() {  }
	void virtual onAfterRender() {  }

};

struct Vertex
{
	GLfloat x, y, z;
	GLfloat r, g, b, a;
	GLfloat nx, ny, nz;
};

class GLVertexArray
{
private:
	GLuint _vertexCount;

public:
	GLVertexArray() { init(); }
	~GLVertexArray() { release();  }

public:
	void init()
	{
		_vertexCount = 0;

		// enable the vertex array
		glEnableClientState(GL_VERTEX_ARRAY); // the vertex data
		glEnableClientState(GL_COLOR_ARRAY); // the color data
		glEnableClientState(GL_NORMAL_ARRAY); // the vertex normal
	}

	void render(GLenum type)
	{
		
	}

	void enable();
	void disable();

	void setData(Vertex* vertices, GLuint vertex_count)
	{
		_vertexCount = vertex_count;

		glVertexPointer(3, GL_FLOAT, sizeof(Vertex), vertices);
		glColorPointer(4, GL_FLOAT, sizeof(Vertex), vertices + 3 * sizeof(GLfloat));
		glNormalPointer(GL_FLOAT, sizeof(Vertex), vertices + 7 * sizeof(GLfloat));
	}

	void release()
	{
		// disable the vertex array
		glDisableClientState(GL_VERTEX_ARRAY);
		glDisableClientState(GL_COLOR_ARRAY);
		glDisableClientState(GL_NORMAL_ARRAY);
	}
};

#pragma endregion

class MyApplication : public GLApplication
{
private:
	GLfloat _spin;
	bool _animate;

public:
	MyApplication() : GLApplication() {}
	virtual ~MyApplication() {}

protected:
	void virtual onAfterInit()
	{
		_animate = false;
		_spin = 0.0;
	}

	void virtual onMouseLeftDown(int x, int y)
	{
		_animate = true;
	}

	void virtual onMouseLeftUp(int x, int y)
	{
		_animate = false;
	}

	void virtual onUpdate(float dt)
	{
		if (_animate)
		{
			_spin = _spin + dt * 100.0f;
			if (_spin > 360.0f)
				_spin = _spin - 360.0f;
		}

		static float dt_sum = 0.0;
		dt_sum += dt;
		if (dt_sum >= 1.0f)
		{
			printf("%f\n", dt_sum / dt);
			dt_sum = 0.0;
		}
	}

	void virtual onRender()
	{
		glPointSize(2.0f);
		glLineWidth(2.0f);
		glLineStipple(1, 0x0101);  // control the pattern of drawn line
		glEnable(GL_LINE_STIPPLE); // enable the line stipple
		glPolygonMode(GL_FRONT, GL_LINE); // draw the model in wireframe
		glFrontFace(GL_CW); // the front face is in clock wise

		glCullFace(GL_BACK); // cull the back face
		glEnable(GL_CULL_FACE); // enable culling
		glEnable(GL_NORMALIZE); // auto normalize normals

		// polygon pattern
		GLubyte fly[] = {
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x03, 0x80, 0x01, 0xC0, 0x06, 0xC0, 0x03, 0x60,
			0x04, 0x60, 0x06, 0x20, 0x04, 0x30, 0x0C, 0x20,
			0x04, 0x18, 0x18, 0x20, 0x04, 0x0C, 0x30, 0x20,
			0x04, 0x06, 0x60, 0x20, 0x44, 0x03, 0xC0, 0x22,
			0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
			0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
			0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
			0x66, 0x01, 0x80, 0x66, 0x33, 0x01, 0x80, 0xCC,
			0x19, 0x81, 0x81, 0x98, 0x0C, 0xC1, 0x83, 0x30,
			0x07, 0xe1, 0x87, 0xe0, 0x03, 0x3f, 0xfc, 0xc0,
			0x03, 0x31, 0x8c, 0xc0, 0x03, 0x33, 0xcc, 0xc0,
			0x06, 0x64, 0x26, 0x60, 0x0c, 0xcc, 0x33, 0x30,
			0x18, 0xcc, 0x33, 0x18, 0x10, 0xc4, 0x23, 0x08,
			0x10, 0x63, 0xC6, 0x08, 0x10, 0x30, 0x0c, 0x08,
			0x10, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00, 0x08 };
		glEnable(GL_POLYGON_STIPPLE); // enable polygin stipple
		glPolygonStipple(fly); // set the polygin stipple

		glPushMatrix();
			glRotatef(_spin, 0.0, 0.0, 1.0);
			glColor3f(1.0, 1.0, 1.0);
			glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
			glBegin(GL_POLYGON);
				glEdgeFlag(GL_TRUE);
				glVertex3f(200.0f, 200.0f, 0.0f);
				glEdgeFlag(GL_FALSE); // mark this edge as not an edge flag (the edge is between this vertex and next)
				glVertex3f(0.0f, 0.0f, 0.0f);
				glEdgeFlag(GL_TRUE);
				glVertex3f(-200.0f, 200.0f, 0.0f);
			glEnd();
		glPopMatrix();

		glDisable(GL_POLYGON_STIPPLE); // disable polygon stipple
	}
};

#pragma region glut init

MyApplication app;

// init the scene
static void init()
{
	app.init();
}

// display the scene
static void display()
{		
	app.render();	
}

static void idle(void)
{
	app.update();
}

static void reshape(int w, int h)
{
	app.reshape(w, h);
}

void mouse(int button, int state, int x, int y)
{
	app.mouse(button, state, x, y);
}

int main_(int argc, char** argv)
{
	// init glut
	glutInit(&argc, argv);

	// setup the window
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowSize(800, 600);
	
	// setup the opengl context
	glutInitContextVersion(4, 3);
	glutInitContextProfile(GLUT_COMPATIBILITY_PROFILE);	

	// create the window
	glutCreateWindow(argv[0]);

	// initialize the glew library
	if (glewInit()) 
		exit(EXIT_FAILURE);

	// disable the vertical sync
	PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL;
	wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
	wglSwapIntervalEXT(0);


	// init the scene
	init();

	// setup the callback functions
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutMouseFunc(mouse);
	glutIdleFunc(idle);

	// enter the main loop
	glutMainLoop();

	return 0;
}

#pragma endregion